#include "../config.h"

#include <gtk/gtk.h>
#include <glibmm/i18n.h>

#include "log.h"
#include "strfuncs.h"
#include "application.h"
#include "command.h"
#include "processview.h"

namespace gpyp {

//========================
// ProcessView::Manager
void ProcessView::Manager::on_output(const Glib::ustring       &text,
                                     Glib::RefPtr<Gtk::TextTag> tag) {
  textbuffer_->insert_with_tag(textbuffer_->end(), text, tag);
}

void ProcessView::Manager::on_run() {
  Glib::ustring msg = format("> %s (%i)\n", proc_->get_command().c_str(), proc_->get_pid());
  on_output(msg, view_.tag_start_);
}
void ProcessView::Manager::on_dead() {
  Glib::ustring msg;
  if (proc_->signal_sent()) {
    msg = "> Signaled\n";
  } else if (proc_->status_exited()) {
    msg = format("> Returned %i\n", proc_->status_exitstatus()).c_str();
  } else {
    msg = "> Aborted\n";
  }
  on_output(msg, view_.tag_end_);
  if (!recorder_) detach_delayed(true);
  view_.list_.queue_draw();
}
void ProcessView::Manager::on_error() {
  if (special_) {
    on_output(_("> Error! Failed to run process.\n"), view_.tag_end_);
  }
}
void ProcessView::Manager::on_stdout(const std::string &text) {
  on_output(text, view_.tag_stdout_);
}
void ProcessView::Manager::on_stderr(const std::string &text) {
  on_output(text, view_.tag_stderr_);
}

void ProcessView::Manager::proc_signals_connect() {
  proc_signals_disconnect();
  conn_run_    = proc_->signal_run()   .connect(sigc::mem_fun(*this, &Manager::on_run));
  conn_dead_   = proc_->signal_dead()  .connect(sigc::mem_fun(*this, &Manager::on_dead));
  conn_error_  = proc_->signal_error() .connect(sigc::mem_fun(*this, &Manager::on_error));
  conn_stdout_ = proc_->signal_stdout().connect(sigc::mem_fun(*this, &Manager::on_stdout));
  conn_stderr_ = proc_->signal_stderr().connect(sigc::mem_fun(*this, &Manager::on_stderr));
}
void ProcessView::Manager::proc_signals_disconnect() {
  conn_run_   .disconnect();
  conn_dead_  .disconnect();
  conn_error_ .disconnect();
  conn_stdout_.disconnect();
  conn_stderr_.disconnect();
}

void ProcessView::Manager::recorder_on_new_process(RefProcess proc) {
  proc_signals_disconnect();
  proc_ = proc;
  if (proc_->alive()) on_run();
  proc_signals_connect();
  view_.list_.queue_draw();
}
void ProcessView::Manager::recorder_on_stop() {
  detach_delayed(true);
  view_.list_.queue_draw();
}

bool ProcessView::Manager::delayed_detach_timeout() {
  detach();
  return false;
}

RefChannel ProcessView::Manager::get_channel() {
  RefChannel ch;
  if (recorder_)    ch = recorder_->get_channel();
  if (!ch && proc_) ch = proc_->get_channel();
  return ch;
}

void ProcessView::Manager::detach() {
  view_.erase(treeiter_);
}
void ProcessView::Manager::detach_delayed(bool check_selected) {
  int d = *view_.conf_->detach_delay();
  if (d < 0 || special_ ||
      (check_selected && view_.selected_.get() == this)) return;

  conn_ddetach_timeout_.disconnect();
  if (d > 0) {
    conn_ddetach_timeout_ = Glib::signal_timeout()
      .connect(sigc::mem_fun(*this, &Manager::delayed_detach_timeout), d * 1000);
  } else {
    detach();
  }
}
void ProcessView::Manager::detach_cancel() {
  conn_ddetach_timeout_.disconnect();
}

ProcessView::Manager::Manager(ProcessView  &view,
                              Gtk::TreeIter tree_iter,
                              RefProcess    proc,
                              bool          special) :
  view_      (view),
  treeiter_  (tree_iter),
  proc_      (proc),
  special_   (special),
  textbuffer_(Gtk::TextBuffer::create(view_.textbuffer_->get_tag_table()))
{
  if (proc_->alive()) on_run();
  proc_signals_connect();
}
ProcessView::Manager::Manager(ProcessView  &view,
                              Gtk::TreeIter tree_iter,
                              RefRecorder   recorder) :
  view_      (view),
  treeiter_  (tree_iter),
  recorder_  (recorder),
  special_   (false),
  textbuffer_(Gtk::TextBuffer::create(view_.textbuffer_->get_tag_table()))
{
  if (recorder->get()) recorder_on_new_process(recorder->get());

  conn_recorder_new_process_ = recorder_->signal_new_process()
    .connect(sigc::mem_fun(*this, &Manager::recorder_on_new_process));
  conn_recorder_stop_ = recorder_->signal_stop()
    .connect(sigc::mem_fun(*this, &Manager::recorder_on_stop));
}
ProcessView::Manager::~Manager() {
  proc_signals_disconnect();
  conn_recorder_new_process_.disconnect();
  conn_recorder_stop_.disconnect();
  conn_ddetach_timeout_.disconnect();
}

//========================
// ProcessView::List
const Gdk::Color ProcessView::List::COLOR_DEAD("gray");

ProcessView::RefManager ProcessView::List::get_manager(const Gtk::TreeIter &iter) {
  return iter ? iter->get_value(model_column_process_) : RefManager();
}
RefProcess ProcessView::List::get_proc(const Gtk::TreeIter &iter) {
  RefManager pm = get_manager(iter);
  return pm ? pm->get() : RefProcess();
}

void ProcessView::List::set_cell_common_data(Gtk::CellRenderer *cell, const Gtk::TreeIter &iter) {
  Gtk::CellRendererText *renderer = dynamic_cast<Gtk::CellRendererText *>(cell);
  RefProcess proc = get_proc(iter);
  renderer->property_foreground_set() = !proc || !proc->alive();
}
void ProcessView::List::pid_on_cell_data(Gtk::CellRenderer *cell, const Gtk::TreeIter &iter) {
  set_cell_common_data(cell, iter);

  Gtk::CellRendererText *renderer =
    dynamic_cast<Gtk::CellRendererText *>(cell);
  RefProcess proc = get_proc(iter);
  pid_t pid = proc ? proc->get_pid() : 0;
  if (pid) {
    renderer->property_text() = tostr(pid);
  } else {
    renderer->property_text() = "-";
  }
}

void ProcessView::List::prepare_commandopts(CommandOpts &opts) {
  RefManager pm = get_selected();
  if (pm) {
    opts.proc = pm->get();
    opts.ch   = pm->get_channel();
  }
}
bool ProcessView::List::on_popup(guint button,
                                 guint32 activate_time) {
  process_menu_.popup(button, activate_time);
  return true;
}

Glib::RefPtr<Gtk::ListStore> ProcessView::List::liststore() {
  return liststore_;
}

ProcessView::RefManager ProcessView::List::get_selected() {
  Gtk::TreeIter iter = get_selection()->get_selected();
  return iter ? iter->get_value(model_column_process_) : RefManager();
}

ProcessView::List::List(Application &app, RefProcessViewElement conf) :
  WidgetInput<TreeView>(app, app.conf().input()->process(), true),
  conf_         (conf),
  column_icon_  ("Icon",    renderer_icon_),
  column_name_  ("Name",    renderer_name_),
  column_chname_("Channel", renderer_chname_),
  column_pid_   ("PID",     renderer_pid_),
  process_menu_ (app, app.conf().menu()->proc(), sigc::mem_fun(*this, &List::prepare_commandopts))
{
  model_columns_.add(model_column_process_);
  model_columns_.add(model_column_icon_);
  model_columns_.add(model_column_name_);
  model_columns_.add(model_column_chname_);
  model_columns_.add(model_column_underline_);
  liststore_ = Gtk::ListStore::create(model_columns_);

  renderer_icon_  .property_xpad() = 0;
  renderer_icon_  .property_ypad() = 0;
  renderer_name_  .property_xpad() = 0;
  renderer_name_  .property_ypad() = 0;
  renderer_name_  .property_foreground_gdk() = COLOR_DEAD;
  renderer_chname_.property_xpad() = 0;
  renderer_chname_.property_ypad() = 0;
  renderer_chname_.property_ellipsize() = Pango::ELLIPSIZE_END;
  renderer_chname_.property_foreground_gdk() = COLOR_DEAD;
  renderer_pid_   .property_xpad() = 0;
  renderer_pid_   .property_ypad() = 0;
  renderer_pid_   .property_xalign() = 1;
  renderer_pid_   .property_foreground_gdk() = COLOR_DEAD;

  column_icon_.add_attribute(renderer_icon_.property_pixbuf(), model_column_icon_);
  column_name_.set_sizing(Gtk::TREE_VIEW_COLUMN_AUTOSIZE);
  column_chname_.set_expand(true);
  column_pid_.set_sizing(Gtk::TREE_VIEW_COLUMN_AUTOSIZE);

  column_name_.add_attribute(renderer_name_.property_text(), model_column_name_);
  column_name_.add_attribute(renderer_name_.property_underline(), model_column_underline_);
  column_name_.set_cell_data_func(renderer_name_, sigc::mem_fun(*this, &List::set_cell_common_data));
  column_chname_.add_attribute(renderer_chname_.property_text(), model_column_chname_);
  column_chname_.add_attribute(renderer_chname_.property_underline(), model_column_underline_);
  column_chname_.set_cell_data_func(renderer_chname_, sigc::mem_fun(*this, &List::set_cell_common_data));
  column_pid_.add_attribute(renderer_pid_.property_underline(), model_column_underline_);
  column_pid_.set_cell_data_func(renderer_pid_, sigc::mem_fun(*this, &List::pid_on_cell_data));

  conf_->show_proc_name()->attach(column_name_.property_visible());

  append_column(column_icon_);
  append_column(column_name_);
  append_column(column_chname_);
  append_column(column_pid_);
  set_model(liststore_);
  set_headers_visible(false);
}

//========================
// ProcessView
ProcessView::RefManager ProcessView::find(RefProcess proc) {
  if (proc) {
    Gtk::TreeNodeChildren children = list_.liststore()->children();
    for(Gtk::TreeIter iter = children.begin();
        iter != children.end(); ++iter) {
      RefManager pm = list_.get_manager(iter);
      if (pm && pm->get() == proc) return pm;
    }
  }
  return RefManager();
}

void ProcessView::erase(Gtk::TreeIter &iter) {
  if (iter) list_.liststore()->erase(iter);
}
ProcessView::RefManager ProcessView::add_process(RefProcess proc, bool special) {
  check_reset_model();

  Gtk::TreeIter iter;
  if (special) {
    iter = list_.liststore()->append();
  } else {
    iter = list_.liststore()->prepend();
  }
  RefManager procmanager(new Manager(*this, iter, proc, special));
  RefChannel ch = proc->get_channel();
  Glib::RefPtr<Gdk::Pixbuf> icon = IconManager::load_icon(proc->get_name(), IconManager::ICON_SIZE_16);
  if (!icon) {
    switch(proc->get_type()) {
    case PROCESS_TYPE_PLAY:
      icon = IconManager::load_icon(Gtk::Stock::MEDIA_PLAY, IconManager::ICON_SIZE_16);
      break;
    case PROCESS_TYPE_SPECIAL_PEERCAST:
      icon = IconManager::load_icon(IconManager::PEERCAST, IconManager::ICON_SIZE_16);
      break;
    case PROCESS_TYPE_SPECIAL_PCRAW_PROXY:
      break;
    default:
      break;
    }
  }
  iter->set_value(list_.model_column_process_,   procmanager);
  iter->set_value(list_.model_column_icon_,      icon);
  iter->set_value(list_.model_column_name_,      proc->get_name());
  iter->set_value(list_.model_column_chname_,    ch ? ch->get_name() : Glib::ustring());
  iter->set_value(list_.model_column_underline_, special ? Pango::UNDERLINE_SINGLE : Pango::UNDERLINE_NONE);
  return procmanager;
}

/* gtk+2.16(2.14?)で、TreeViewにモデルを設定したままモデルに手を加えると
   際限なくメモリを消費するので定期的にリセットする。 */
void ProcessView::check_reset_model() {
  ++reset_model_count_;
  if (reset_model_count_ > 7) {
    list_.unset_model();
    list_.set_model(list_.liststore_);
    reset_model_count_ = 0;
  }
}

void ProcessView::commandrunner_on_new_process(RefProcess proc) {
  add_process(proc, false);
}
void ProcessView::commandrunner_on_run_peercast(RefProcess proc) {
  if (proc_peercast_) erase(proc_peercast_->treeiter_);
  proc_peercast_ = add_process(proc, true);
}
void ProcessView::commandrunner_on_run_pcraw_proxy(RefProcess proc) {
  if (proc_pcraw_proxy_) erase(proc_pcraw_proxy_->treeiter_);
  proc_pcraw_proxy_ = add_process(proc, true);
}
void ProcessView::commandrunner_on_record(RefRecorder recorder) {
  check_reset_model();

  Gtk::TreeIter iter = list_.liststore()->prepend();
  RefManager procmanager(new Manager(*this, iter, recorder));
  RefChannel ch = recorder->get_channel();
  Glib::RefPtr<Gdk::Pixbuf> icon = IconManager::load_icon(Gtk::Stock::MEDIA_RECORD, IconManager::ICON_SIZE_16);
  iter->set_value(list_.model_column_process_,   procmanager);
  iter->set_value(list_.model_column_icon_,      icon);
  iter->set_value(list_.model_column_name_,      Glib::ustring("Record"));
  iter->set_value(list_.model_column_chname_,    ch ? ch->get_name() : Glib::ustring());
  iter->set_value(list_.model_column_underline_, Pango::UNDERLINE_SINGLE);
}

void ProcessView::list_selection_on_changed() {
  RefManager pm = list_.get_selected();
  if (selected_ && selected_ != pm) {
    if (selected_->recorder_) {
      if (selected_->recorder_->is_stopped()) selected_->detach_delayed();
    } else if (!selected_->get()->alive()) {
      selected_->detach_delayed();
    }
  }
  if (pm) {
    pm->detach_cancel();
    outputview_.set_buffer(pm->textbuffer_);
  } else {
    outputview_.set_buffer(textbuffer_);
  }
  selected_ = pm;
}
void ProcessView::outputview_on_set_scroll_adjustments(Gtk::Adjustment */*hadjustment*/,
                                                       Gtk::Adjustment *vadjustment) {
  conn_outputview_vadjustment_changed_.disconnect();
  if (vadjustment) {
    conn_outputview_vadjustment_changed_ = vadjustment->signal_changed()
      .connect(sigc::mem_fun(*this, &ProcessView::outputview_vadjustment_on_changed));
  }
}
void ProcessView::outputview_vadjustment_on_changed() {
  Gtk::TextIter iter = outputview_.get_buffer()->end();
  outputview_.scroll_to(iter, 0);
}

void ProcessView::detach(RefProcess proc) {
  RefManager pm = find(proc);
  if (pm && pm != selected_) pm->detach();
}

ProcessView::ProcessView(Application &app, RefProcessViewElement conf) :
  panel::PagePaned<Gtk::HPaned>(conf),
  app_              (app),
  conf_             (conf),
  list_             (app_, conf_),
  outputview_       (app_, true),
  textbuffer_       (Gtk::TextBuffer::create()),
  bc_processview_   (*app_.bc().add_ns("ProcessView")),
  reset_model_count_(0)
{
#define TAG_ATTACH_COLOR(N, T) {                 \
    app_.conf().general()->color_##N()           \
      ->attach(T->property_foreground_gdk());    \
    app_.conf().general()->color_##N()           \
      ->attach(T->property_foreground_set());    \
  }
  tag_start_  = textbuffer_->create_tag();
  tag_end_    = textbuffer_->create_tag();
  tag_stdout_ = textbuffer_->create_tag();
  tag_stderr_ = textbuffer_->create_tag();
  TAG_ATTACH_COLOR(unimportant, tag_start_);
  TAG_ATTACH_COLOR(unimportant, tag_end_);
  TAG_ATTACH_COLOR(positive,    tag_stdout_);
  TAG_ATTACH_COLOR(negative,    tag_stderr_);
#undef TAG_ATTACH_COLOR

  list_.get_selection()->signal_changed()
    .connect(sigc::mem_fun(*this, &ProcessView::list_selection_on_changed));

  outputview_.set_buffer(textbuffer_);
  outputview_.set_editable(false);
  outputview_.set_wrap_mode(Gtk::WRAP_CHAR);
  outputview_.signal_set_scroll_adjustments()
    .connect(sigc::mem_fun(*this, &ProcessView::outputview_on_set_scroll_adjustments));

  pack1(list_, false, false, true);
  pack2(outputview_, true, false, true);

  CommandRunner &cr = app_.commandrunner();
  const CommandRunner::ProcessList  &pl = cr.get_processes();
  const CommandRunner::RecorderList &rl = cr.get_recorders();
  for(CommandRunner::ProcessList::const_iterator iter = pl.begin(); iter != pl.end(); ++iter) {
    add_process(iter->proc_, false);
  }
  if (cr.peercast())    proc_peercast_    = add_process(cr.peercast(),    true);
  if (cr.pcraw_proxy()) proc_pcraw_proxy_ = add_process(cr.pcraw_proxy(), true);
  for(CommandRunner::RecorderList::const_iterator iter = rl.begin(); iter != rl.end(); ++iter) {
    commandrunner_on_record(iter->recorder_);
  }

  conn_commandrunner_new_process_ = cr.signal_new_process()
    .connect(sigc::mem_fun(*this, &ProcessView::commandrunner_on_new_process));
  conn_commandrunner_run_peercast_ = cr.signal_run_peercast()
    .connect(sigc::mem_fun(*this, &ProcessView::commandrunner_on_run_peercast));
  conn_commandrunner_run_pcraw_proxy_ = cr.signal_run_pcraw_proxy()
    .connect(sigc::mem_fun(*this, &ProcessView::commandrunner_on_run_pcraw_proxy));
  conn_commandrunner_record_ = cr.signal_record()
    .connect(sigc::mem_fun(*this, &ProcessView::commandrunner_on_record));
}
ProcessView::~ProcessView() {
  app_.bc().remove(bc_processview_);

  conn_outputview_vadjustment_changed_.disconnect();

  conn_commandrunner_new_process_    .disconnect();
  conn_commandrunner_run_peercast_   .disconnect();
  conn_commandrunner_run_pcraw_proxy_.disconnect();
  conn_commandrunner_record_         .disconnect();
}

}/*gpyp*/
